﻿using System;
using System.Collections.Generic;
using System.IO;
using EmbeddedSharp.Core;
using EmbeddedSharp.Core.Logging;
using Zove.Runtime.Core.EquationTransformer;

namespace Zove.Runtime.Core {
    /// <summary>
    /// 
    /// </summary>
    public class DefaultRuntimeModelLoader : IRuntimeModelLoader {
        /// <summary>
        /// Previous loaded classes
        /// </summary>
        protected readonly List<string> PreviousLoadedClasses = new List<string>();

        /// <summary>
        /// Default constructor
        /// </summary>
        private DefaultRuntimeModelLoader() {
            AssociatedClasses = new Dictionary<string, AssociatedClass>();
        }

        /// <summary>
        /// Language nature specified
        /// </summary>
        /// <param name="languageNature">language nature</param>
        public DefaultRuntimeModelLoader(ILanguageNature languageNature)
            : this() {
            LanguageNature = languageNature;
        }

        /// <summary>
        /// The language nature
        /// </summary>
        public ILanguageNature LanguageNature { get; private set; }

        /// <summary>
        /// Class loader
        /// </summary>
        public IClassLoader ClassLoader { get; set; }

        /// <summary>
        /// Associated classes
        /// </summary>
        public Dictionary<string, AssociatedClass> AssociatedClasses { get; set; }

        #region IRuntimeModelLoader Members

        /// <summary>
        /// Load the class
        /// </summary>
        /// <param name="className">the name of the class</param>
        /// <returns>the runtime model</returns>
        public RuntimeModel LoadByClassName(string className) {
            Reset();
            return LoadByClassName("", className);
        }

        /// <summary>
        /// Load by source
        /// </summary>
        /// <param name="source">source</param>
        /// <returns>runtime model</returns>
        public RuntimeModel LoadBySource(string source) {
            Reset();

            ZoomClass c = LanguageNature.ClassLoader.LoadFromSource(source);
            return LoadByClass(c);
        }

        /// <summary>
        /// Load by source
        /// </summary>
        /// <param name="instanceName">instance name</param>
        /// <param name="source">class source</param>
        /// <returns>runtime model</returns>
        public RuntimeModel LoadBySource(string instanceName, string source) {
            Reset();

            ZoomClass c = LanguageNature.ClassLoader.LoadFromSource(source);
            return LoadByClass(instanceName, c);
        }

        /// <summary>
        /// Load by class name
        /// </summary>
        /// <param name="instanceName">the instance name</param>
        /// <param name="className">the class name</param>
        /// <returns>the runtime model</returns>
        public RuntimeModel LoadByClassName(string instanceName, string className) {
            Reset();

            ZoomClass zvc = LanguageNature.ClassLoader.LoadByClassName(className);
            RuntimeModel rm = InternalLoad(instanceName, zvc);

            return rm;
        }

        #endregion

        /// <summary>
        /// Load by source
        /// </summary>
        /// <param name="provider">text source provider</param>
        /// <returns>the runtime model</returns>
        public RuntimeModel Load(ITextContentProvider provider) {
            string source = provider.SourceReader.ReadToEnd();
            return LoadBySource(source);
        }

        /// <summary>
        /// Load by file of the url
        /// </summary>
        /// <param name="url">the location</param>
        /// <returns>the runtime model</returns>
        public RuntimeModel LoadByUrl(string url) {
            Reset();
            string source = File.ReadAllText(url);
            return LoadBySource(source);
        }

        /// <summary>
        /// Load by class name
        /// </summary>
        /// <param name="c">the class</param>
        /// <returns>the runtime model</returns>
        public RuntimeModel LoadByClass(ZoomClass c) {
            Reset();
            return LoadByClass("", c);
        }

        /// <summary>
        /// Load internally
        /// </summary>
        /// <param name="instanceName">instance name</param>
        /// <param name="zc">zoom class instance</param>
        /// <returns>runtime model</returns>
        protected RuntimeModel InternalLoad(string instanceName, ZoomClass zc) {
            if (zc == null) {
                throw new ArgumentNullException("zc", "ZoomClass is null!");
            }

            Utils.Debugging("loading class {0}", zc.Name);
            if (PreviousLoadedClasses.Contains(zc.Name)) {
                throw new RecursiveDefinedClassException(zc.Name);
            }
            PreviousLoadedClasses.Add(zc.Name);

            AssociatedClass a;
            if (!AssociatedClasses.ContainsKey(zc.Name)) {
                a = new AssociatedClass { Class = zc, CalledTimes = 1 };
                AssociatedClasses.Add(zc.Name, a);
            } else {
                a = AssociatedClasses[zc.Name];
                a.CalledTimes++;
            }

            RuntimeModel rm = LoadByClass(instanceName, zc);

            PreviousLoadedClasses.Remove(zc.Name);

            return rm;
        }

        /// <summary>
        /// Check if the key is inside the array
        /// </summary>
        /// <param name="keys">the array</param>
        /// <param name="key">the query</param>
        /// <returns>true if contains</returns>
        public static bool Contains(string[] keys, string key) {
            foreach (string k in keys) { if (k == key) return true; }
            return false;
        }

        /// <summary>
        /// mat is a dyn type loads
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="zc"></param>
        /// <returns></returns>
        protected RuntimeModel LoadByClass(
            string prefix, ZoomClass zc) {
            var rm = new RuntimeModel { Name = zc.Name };

            if (zc.BaseClasses.Count > 0) {
                foreach (string bc in zc.BaseClasses) {
                    RuntimeModel parent
                        = LoadByClassName(prefix, bc);
                    rm.Add(parent);
                }
            }

            foreach (ZoomVariable var in zc.Variables) {
                string p =
                    string.IsNullOrEmpty(prefix)
                        ? var.Name
                        : prefix + "." + var.Name;
                if (Contains(LanguageNature.AtomicTypes, var.Type)) {
                    RuntimeFactor rf = rm.AddFactor(p);
                    rf.Unit = var.Unit;
                } else {
                    RuntimeModel m = LoadByClassName(p, var.Type);
                    rm.Add(m);
                }
            }

            foreach (ZoomEquation equ in zc.Equations) {
                string equation = Utils.Prefixize(
                    prefix, equ.Equation,
                    LanguageNature.FunctionManager);
                equation = PreprocessEquation(zc, rm, equation);
                var re = new RuntimeEquation(equation);
                rm.Equations.Add(re);

                var et
                    = new DefaultEquationTransformer(re.Equation, rm, LanguageNature);
                re.Left = et.LeftTree;
                re.Right = et.RightTree;

                foreach (ZoomElement e in equ.Elements) {
                    //if (ZoomDependencyManager.Instance.ConstantManager.IsConstant(e.Name)) {
                    //    continue;
                    //}

                    string m = string.IsNullOrEmpty(prefix)
                                   ? e.Name
                                   : prefix + "." + e.Name;
                    RuntimeFactor rf = rm.Factors[m];
                    if (rf == null) {
                        //throw new FactorNotDeclaredException(
                        //    e.Name, equ.Equation, zvc.Name);
                        var msg = new LoggingMessage
                                      {
                                          Description = string.Format(
                                              "The variable {0} used in equation {1} is not declared",
                                              e.Name, equ.Equation),
                                          FileName = zc.Name,
                                          LineNumber = equ.LineNumber
                                      };

                        DefaultDependencyManager.Instance.Logger.WriteLog(msg.Description);
                        continue;
                    }
                    re.Factors.Add(rf);
                    rf.Equations.Add(re);

                    var rr = new RuntimeRule
                                 {
                                     Factor = rf,
                                     Equation = re,
                                     Root = et.Nodes.ContainsKey(rf.Name) ? et.Nodes[rf.Name] : null
                                 };
                    rf.Rules.Add(rr);

                    string rrId = RuntimeModel.GetRuleId(re, rf);
                    rm.Rules[rrId] = rr;
                }
            }

            foreach (RuntimeFactor rf in rm.Factors) {
                foreach (RuntimeRule rr in rf.Rules) {
                    if (rr.Equation.Factors.Count == 1) {
                        //double value = Convert.ToDouble(rr.Root.Evaluate());
                        rf.Solve(rr);
                    }
                }
            }

            return rm;
        }

        /// <summary>
        /// Replace the function calls in the expression 
        /// with the function bodies
        /// </summary>
        /// <param name="zc">the class model</param>
        /// <param name="rm">the runtime model</param>
        /// <param name="equation">the equation</param>
        /// <returns>the resulted expression</returns>
        public string PreprocessEquation(
            ZoomClass zc, RuntimeModel rm, string equation) {
            var converter
                = new UserDefinedFunctionConverter(zc.Name);

            string[] sides = equation.Split('=');
            string lhs = PreprocessExpr(zc, rm, converter, sides[0]);
            string rhs = PreprocessExpr(zc, rm, converter, sides[1]);
            equation = lhs + "=" + rhs;

            return equation;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="zc"></param>
        /// <param name="rm"></param>
        /// <param name="converter"></param>
        /// <param name="expr"></param>
        /// <returns></returns>
        public string PreprocessExpr(
            ZoomClass zc, RuntimeModel rm,
            UserDefinedFunctionConverter converter, string expr) {
            // if the function is declared in its class
            // place the class name in the front of the function call
            // to identify it
            expr = Utils.PrefixizeFunction(zc.Name, expr, LanguageNature.FunctionManager);

            ExpressionParser lp
                = LanguageNature.ExpressionParserFactory.CreateParser(
                    expr, rm);
            Node lrx = lp.Parse();
            string lhs = converter.Convert(lrx);

            return lhs;
        }

        /// <summary>
        /// Cleans the class loading footprints
        /// </summary>
        public void Reset() {
            PreviousLoadedClasses.Clear();
        }

        #region Nested type: AssociatedClass

        /// <summary>
        /// 
        /// </summary>
        public class AssociatedClass {
            public ZoomClass Class { get; set; }

            public int CalledTimes { get; set; }
        }

        #endregion
    }
}
